home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1993 July / InfoMagic USENET CD-ROM July 1993.ISO / sources / unix / volume23 / vixie-cron / part02 < prev    next >
Encoding:
Internet Message Format  |  1990-10-09  |  52.4 KB

  1. Subject:  v23i029:  A cron/crontab replacement, Part02/02
  2. Newsgroups: comp.sources.unix
  3. Approved: rsalz@uunet.UU.NET
  4. X-Checksum-Snefru: 5d871866 eeecbfc3 848fc0b0 e2cdd96b
  5.  
  6. Submitted-by: Paul A Vixie <vixie@vixie.sf.ca.us>
  7. Posting-number: Volume 23, Issue 29
  8. Archive-name: vixie-cron/part02
  9.  
  10. #! /bin/sh
  11. # This is a shell archive.  Remove anything before this line, then unpack
  12. # it by saving it into a file and typing "sh file".  To overwrite existing
  13. # files, type "sh file -c".  You can also feed this as standard input via
  14. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  15. # will see the following message at the end:
  16. #        "End of archive 2 (of 3)."
  17. # Contents:  cron.h crond.c crontab.5 crontab.c database.c entry.c
  18. # Wrapped by vixie@volition.pa.dec.com on Wed Jul 18 00:32:48 1990
  19. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  20. if test -f 'cron.h' -a "${1}" != "-c" ; then 
  21.   echo shar: Will not clobber existing file \"'cron.h'\"
  22. else
  23. echo shar: Extracting \"'cron.h'\" \(7161 characters\)
  24. sed "s/^X//" >'cron.h' <<'END_OF_FILE'
  25. X/* cron.h - header for vixie's cron
  26. X *
  27. X * $Header: cron.h,v 2.1 90/07/18 00:23:47 vixie Exp $
  28. X * $Source: /jove_u3/vixie/src/cron/RCS/cron.h,v $
  29. X * $Revision: 2.1 $
  30. X * $Log:    cron.h,v $
  31. X * Revision 2.1  90/07/18  00:23:47  vixie
  32. X * Baseline for 4.4BSD release
  33. X * 
  34. X * Revision 2.0  88/12/10  04:57:39  vixie
  35. X * V2 Beta
  36. X * 
  37. X * Revision 1.2  88/11/29  13:05:46  vixie
  38. X * seems to work on Ultrix 3.0 FT1
  39. X * 
  40. X * Revision 1.1  88/11/14  12:27:49  vixie
  41. X * Initial revision
  42. X * 
  43. X * Revision 1.4  87/05/02  17:33:08  paul
  44. X * baseline for mod.sources release
  45. X *
  46. X * vix 14jan87 [0 or 7 can be sunday; thanks, mwm@berkeley]
  47. X * vix 30dec86 [written]
  48. X */
  49. X
  50. X/* Copyright 1988,1990 by Paul Vixie
  51. X * All rights reserved
  52. X *
  53. X * Distribute freely, except: don't remove my name from the source or
  54. X * documentation (don't take credit for my work), mark your changes (don't
  55. X * get me blamed for your possible bugs), don't alter or remove this
  56. X * notice.  May be sold if buildable source is provided to buyer.  No
  57. X * warrantee of any kind, express or implied, is included with this
  58. X * software; use at your own risk, responsibility for damages (if any) to
  59. X * anyone resulting from the use of this software rests entirely with the
  60. X * user.
  61. X *
  62. X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
  63. X * I'll try to keep a version up to date.  I can be reached as follows:
  64. X * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
  65. X * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
  66. X */
  67. X
  68. X#ifndef    _CRON_FLAG
  69. X#define    _CRON_FLAG
  70. X
  71. X#include <stdio.h>
  72. X#include <ctype.h>
  73. X#include <bitstring.h>
  74. X#include <sys/types.h>
  75. X#include <sys/stat.h>
  76. X
  77. X#include "config.h"
  78. X
  79. X    /* these are really immutable, and are
  80. X     *   defined for symbolic convenience only
  81. X     * TRUE, FALSE, and ERR must be distinct
  82. X     */
  83. X#define TRUE        1
  84. X#define FALSE        0
  85. X    /* system calls return this on success */
  86. X#define OK        0
  87. X    /*   or this on error */
  88. X#define ERR        (-1)
  89. X
  90. X    /* meaningless cookie for smart compilers that will pick their
  91. X     * own register variables; this makes the code neater.
  92. X     */
  93. X#define    local        /**/
  94. X
  95. X    /* turn this on to get '-x' code */
  96. X#ifndef DEBUGGING
  97. X#define DEBUGGING    FALSE
  98. X#endif
  99. X
  100. X#define READ_PIPE    0    /* which end of a pipe pair do you read? */
  101. X#define WRITE_PIPE    1    /*   or write to? */
  102. X#define STDIN        0    /* what is stdin's file descriptor? */
  103. X#define STDOUT        1    /*   stdout's? */
  104. X#define STDERR        2    /*   stderr's? */
  105. X#define ERROR_EXIT    1    /* exit() with this will scare the shell */
  106. X#define    OK_EXIT        0    /* exit() with this is considered 'normal' */
  107. X#define    MAX_FNAME    100    /* max length of internally generated fn */
  108. X#define    MAX_COMMAND    1000    /* max length of internally generated cmd */
  109. X#define    MAX_ENVSTR    1000    /* max length of envvar=value\0 strings */
  110. X#define    MAX_TEMPSTR    100    /* obvious */
  111. X#define    MAX_UNAME    20    /* max length of username, should be overkill */
  112. X#define    ROOT_UID    0    /* don't change this, it really must be root */
  113. X#define    ROOT_USER    "root"    /* ditto */
  114. X
  115. X                /* NOTE: these correspond to DebugFlagNames,
  116. X                 *    defined below.
  117. X                 */
  118. X#define    DEXT        0x0001    /* extend flag for other debug masks */
  119. X#define    DSCH        0x0002    /* scheduling debug mask */
  120. X#define    DPROC        0x0004    /* process control debug mask */
  121. X#define    DPARS        0x0008    /* parsing debug mask */
  122. X#define    DLOAD        0x0010    /* database loading debug mask */
  123. X#define    DMISC        0x0020    /* misc debug mask */
  124. X#define    DTEST        0x0040    /* test mode: don't execute any commands */
  125. X
  126. X                /* the code does not depend on any of vfork's
  127. X                 * side-effects; it just uses it as a quick
  128. X                 * fork-and-exec.
  129. X                 */
  130. X#if defined(BSD)
  131. X# define VFORK        vfork
  132. X#endif
  133. X#if defined(ATT)
  134. X# define VFORK        fork
  135. X#endif
  136. X
  137. X#define    CRON_TAB(u)    "%s/%s", SPOOL_DIR, u
  138. X#define    REG        register
  139. X#define    PPC_NULL    ((char **)NULL)
  140. X
  141. X#ifndef MAXHOSTNAMELEN
  142. X#define MAXHOSTNAMELEN 64
  143. X#endif
  144. X
  145. X#define    Skip_Blanks(c, f) \
  146. X            while (c == '\t' || c == ' ') \
  147. X                c = get_char(f);
  148. X
  149. X#define    Skip_Nonblanks(c, f) \
  150. X            while (c!='\t' && c!=' ' && c!='\n' && c != EOF) \
  151. X                c = get_char(f);
  152. X
  153. X#define    Skip_Line(c, f) \
  154. X            do {c = get_char(f);} while (c != '\n' && c != EOF);
  155. X
  156. X#if DEBUGGING
  157. X# define Debug(mask, message) \
  158. X            if ( (DebugFlags & (mask) ) == (mask) ) \
  159. X                printf message;
  160. X#else /* !DEBUGGING */
  161. X# define Debug(mask, message) \
  162. X            ;
  163. X#endif /* DEBUGGING */
  164. X
  165. X#define    MkLower(ch)    (isupper(ch) ? tolower(ch) : ch)
  166. X#define    MkUpper(ch)    (islower(ch) ? toupper(ch) : ch)
  167. X#define    Set_LineNum(ln)    {Debug(DPARS|DEXT,("linenum=%d\n",ln)); \
  168. X             LineNumber = ln; \
  169. X            }
  170. X
  171. X#define    FIRST_MINUTE    0
  172. X#define    LAST_MINUTE    59
  173. X#define    MINUTE_COUNT    (LAST_MINUTE - FIRST_MINUTE + 1)
  174. X
  175. X#define    FIRST_HOUR    0
  176. X#define    LAST_HOUR    23
  177. X#define    HOUR_COUNT    (LAST_HOUR - FIRST_HOUR + 1)
  178. X
  179. X#define    FIRST_DOM    1
  180. X#define    LAST_DOM    31
  181. X#define    DOM_COUNT    (LAST_DOM - FIRST_DOM + 1)
  182. X
  183. X#define    FIRST_MONTH    1
  184. X#define    LAST_MONTH    12
  185. X#define    MONTH_COUNT    (LAST_MONTH - FIRST_MONTH + 1)
  186. X
  187. X/* note on DOW: 0 and 7 are both Sunday, for compatibility reasons. */
  188. X#define    FIRST_DOW    0
  189. X#define    LAST_DOW    7
  190. X#define    DOW_COUNT    (LAST_DOW - FIRST_DOW + 1)
  191. X
  192. X            /* each user's crontab will be held as a list of
  193. X             * the following structure.
  194. X             *
  195. X             * These are the cron commands.
  196. X             */
  197. X
  198. typedef    struct    _entry
  199. X    {
  200. X        struct _entry    *next;
  201. X        char        *cmd;
  202. X        bitstr_t    bit_decl(minute, MINUTE_COUNT);
  203. X        bitstr_t    bit_decl(hour,   HOUR_COUNT);
  204. X        bitstr_t    bit_decl(dom,    DOM_COUNT);
  205. X        bitstr_t    bit_decl(month,  MONTH_COUNT);
  206. X        bitstr_t    bit_decl(dow,    DOW_COUNT);
  207. X        int        flags;
  208. X# define    DOM_STAR    0x1
  209. X# define    DOW_STAR    0x2
  210. X# define    WHEN_REBOOT    0x4
  211. X    }
  212. X    entry;
  213. X
  214. X            /* the crontab database will be a list of the
  215. X             * following structure, one element per user.
  216. X             *
  217. X             * These are the crontabs.
  218. X             */
  219. X
  220. typedef    struct    _user
  221. X    {
  222. X        struct _user    *next, *prev;    /* links */
  223. X        int        uid;        /* uid from passwd file */
  224. X        int        gid;        /* gid from passwd file */
  225. X        char        **envp;        /* environ for commands */
  226. X        time_t        mtime;        /* last modtime of crontab */
  227. X        entry        *crontab;    /* this person's crontab */
  228. X    }
  229. X    user;
  230. X
  231. typedef    struct    _cron_db
  232. X    {
  233. X        user        *head, *tail;    /* links */
  234. X        time_t        mtime;        /* last modtime on spooldir */
  235. X    }
  236. X    cron_db;
  237. X
  238. X                /* in the C tradition, we only create
  239. X                 * variables for the main program, just
  240. X                 * extern them elsewhere.
  241. X                 */
  242. X
  243. X#ifdef MAIN_PROGRAM
  244. X
  245. X# if !defined(LINT) && !defined(lint)
  246. X        static char *copyright[] = {
  247. X            "@(#) Copyright (C) 1988, 1989, 1990 by Paul Vixie",
  248. X            "@(#) All rights reserved"
  249. X        };
  250. X# endif
  251. X
  252. X        char *MonthNames[] = {
  253. X            "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  254. X            "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
  255. X            NULL};
  256. X        char *DowNames[] = {
  257. X            "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
  258. X            NULL};
  259. X        char *ProgramName;
  260. X        int LineNumber;
  261. X        time_t TargetTime;
  262. X
  263. X# if DEBUGGING
  264. X        int DebugFlags;
  265. X        char *DebugFlagNames[] = {    /* sync with #defines */
  266. X            "ext", "sch", "proc", "pars", "load", "misc", "test",
  267. X            NULL};    /* NULL must be last element */
  268. X# endif /* DEBUGGING */
  269. X
  270. X#else /* !MAIN_PROGRAM */
  271. X
  272. X        extern char    *MonthNames[];
  273. X        extern char    *DowNames[];
  274. X        extern char    *ProgramName;
  275. X        extern int    LineNumber;
  276. X        extern time_t    TargetTime;
  277. X# if DEBUGGING
  278. X        extern int    DebugFlags;
  279. X        extern char    *DebugFlagNames[];
  280. X# endif /* DEBUGGING */
  281. X#endif /* MAIN_PROGRAM */
  282. X
  283. X
  284. X#endif    /* _CRON_FLAG */
  285. END_OF_FILE
  286. if test 7161 -ne `wc -c <'cron.h'`; then
  287.     echo shar: \"'cron.h'\" unpacked with wrong size!
  288. fi
  289. # end of 'cron.h'
  290. fi
  291. if test -f 'crond.c' -a "${1}" != "-c" ; then 
  292.   echo shar: Will not clobber existing file \"'crond.c'\"
  293. else
  294. echo shar: Extracting \"'crond.c'\" \(7067 characters\)
  295. sed "s/^X//" >'crond.c' <<'END_OF_FILE'
  296. X#if !defined(lint) && !defined(LINT)
  297. static char rcsid[] = "$Header: crond.c,v 2.1 90/07/18 00:23:53 vixie Exp $";
  298. X#endif
  299. X
  300. X/* Copyright 1988,1990 by Paul Vixie
  301. X * All rights reserved
  302. X *
  303. X * Distribute freely, except: don't remove my name from the source or
  304. X * documentation (don't take credit for my work), mark your changes (don't
  305. X * get me blamed for your possible bugs), don't alter or remove this
  306. X * notice.  May be sold if buildable source is provided to buyer.  No
  307. X * warrantee of any kind, express or implied, is included with this
  308. X * software; use at your own risk, responsibility for damages (if any) to
  309. X * anyone resulting from the use of this software rests entirely with the
  310. X * user.
  311. X *
  312. X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
  313. X * I'll try to keep a version up to date.  I can be reached as follows:
  314. X * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
  315. X * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
  316. X */
  317. X
  318. X
  319. X#define    MAIN_PROGRAM
  320. X
  321. X
  322. X#include "cron.h"
  323. X#include <sys/time.h>
  324. X#include <sys/signal.h>
  325. X#include <sys/types.h>
  326. X#if defined(BSD)
  327. X# include <sys/wait.h>
  328. X# include <sys/resource.h>
  329. X#endif /*BSD*/
  330. X
  331. extern int    fprintf(), fork(), unlink();
  332. extern time_t    time();
  333. extern void    exit();
  334. extern unsigned    sleep();
  335. X
  336. void
  337. usage()
  338. X{
  339. X    (void) fprintf(stderr, "usage:  %s [-x debugflag[,...]]\n", ProgramName);
  340. X    (void) exit(ERROR_EXIT);
  341. X}
  342. X
  343. X
  344. int
  345. main(argc, argv)
  346. X    int    argc;
  347. X    char    *argv[];
  348. X{
  349. X    extern void    set_cron_uid(), be_different(), load_database(),
  350. X            set_cron_cwd(), open_logfile();
  351. X
  352. X    static void    cron_tick(), cron_sleep(), cron_sync(),
  353. X            sigchld_handler(), parse_args(), run_reboot_jobs();
  354. X
  355. X    auto cron_db    database;
  356. X
  357. X    ProgramName = argv[0];
  358. X
  359. X#if defined(BSD)
  360. X    setlinebuf(stdout);
  361. X    setlinebuf(stderr);
  362. X#endif
  363. X
  364. X    parse_args(argc, argv);
  365. X
  366. X# if DEBUGGING
  367. X    /* if there are no debug flags turned on, fork as a daemon should.
  368. X     */
  369. X    if (DebugFlags)
  370. X    {
  371. X        (void) fprintf(stderr, "[%d] crond started\n", getpid());
  372. X    }
  373. X    else
  374. X# endif /*DEBUGGING*/
  375. X    {
  376. X        switch (fork())
  377. X        {
  378. X        case -1:
  379. X            log_it("CROND",getpid(),"DEATH","can't fork");
  380. X            exit(0);
  381. X            break;
  382. X        case 0:
  383. X            /* child process */
  384. X            be_different();
  385. X            break;
  386. X        default:
  387. X            /* parent process should just die */
  388. X            _exit(0);
  389. X        }
  390. X    }
  391. X
  392. X#if defined(BSD)
  393. X    (void) signal(SIGCHLD, sigchld_handler);
  394. X#endif /*BSD*/
  395. X
  396. X#if defined(ATT)
  397. X    (void) signal(SIGCLD, SIG_IGN);
  398. X#endif /*ATT*/
  399. X
  400. X    acquire_daemonlock();
  401. X    set_cron_uid();
  402. X    set_cron_cwd();
  403. X    database.head = NULL;
  404. X    database.tail = NULL;
  405. X    database.mtime = (time_t) 0;
  406. X    load_database(&database);
  407. X    run_reboot_jobs(&database);
  408. X    cron_sync();
  409. X    while (TRUE)
  410. X    {
  411. X# if DEBUGGING
  412. X        if (!(DebugFlags & DTEST))
  413. X# endif /*DEBUGGING*/
  414. X            cron_sleep();
  415. X
  416. X        load_database(&database);
  417. X
  418. X        /* do this iteration
  419. X         */
  420. X        cron_tick(&database);
  421. X
  422. X        /* sleep 1 minute
  423. X         */
  424. X        TargetTime += 60;
  425. X    }
  426. X}
  427. X
  428. X
  429. static void
  430. run_reboot_jobs(db)
  431. X    cron_db *db;
  432. X{
  433. X    extern void        job_add();
  434. X    extern int        job_runqueue();
  435. X    register user        *u;
  436. X    register entry        *e;
  437. X
  438. X    for (u = db->head;  u != NULL;  u = u->next) {
  439. X        for (e = u->crontab;  e != NULL;  e = e->next) {
  440. X            if (e->flags & WHEN_REBOOT) {
  441. X                job_add(e->cmd, u);
  442. X            }
  443. X        }
  444. X    }
  445. X    (void) job_runqueue();
  446. X}
  447. X
  448. X
  449. static void
  450. cron_tick(db)
  451. X    cron_db    *db;
  452. X{
  453. X    extern void        job_add();
  454. X    extern char        *env_get();
  455. X    extern struct tm    *localtime();
  456. X     register struct tm    *tm = localtime(&TargetTime);
  457. X    local int        minute, hour, dom, month, dow;
  458. X    register user        *u;
  459. X    register entry        *e;
  460. X
  461. X    /* make 0-based values out of these so we can use them as indicies
  462. X     */
  463. X    minute = tm->tm_min -FIRST_MINUTE;
  464. X    hour = tm->tm_hour -FIRST_HOUR;
  465. X    dom = tm->tm_mday -FIRST_DOM;
  466. X    month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
  467. X    dow = tm->tm_wday -FIRST_DOW;
  468. X
  469. X    Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d)\n",
  470. X        getpid(), minute, hour, dom, month, dow))
  471. X
  472. X    /* the dom/dow situation is odd.  '* * 1,15 * Sun' will run on the
  473. X     * first and fifteenth AND every Sunday;  '* * * * Sun' will run *only*
  474. X     * on Sundays;  '* * 1,15 * *' will run *only* the 1st and 15th.  this
  475. X     * is why we keep 'e->dow_star' and 'e->dom_star'.  yes, it's bizarre.
  476. X     * like many bizarre things, it's the standard.
  477. X     */
  478. X    for (u = db->head;  u != NULL;  u = u->next) {
  479. X        Debug(DSCH|DEXT, ("user [%s:%d:%d:...]\n",
  480. X            env_get(USERENV,u->envp), u->uid, u->gid))
  481. X        for (e = u->crontab;  e != NULL;  e = e->next) {
  482. X            Debug(DSCH|DEXT, ("entry [%s]\n", e->cmd))
  483. X            if (bit_test(e->minute, minute)
  484. X             && bit_test(e->hour, hour)
  485. X             && bit_test(e->month, month)
  486. X             && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
  487. X                  ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
  488. X                  : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
  489. X                )
  490. X               ) {
  491. X                job_add(e->cmd, u);
  492. X            }
  493. X        }
  494. X    }
  495. X}
  496. X
  497. X
  498. X/* the task here is to figure out how long it's going to be until :00 of the
  499. X * following minute and initialize TargetTime to this value.  TargetTime
  500. X * will subsequently slide 60 seconds at a time, with correction applied
  501. X * implicitly in cron_sleep().  it would be nice to let crond execute in
  502. X * the "current minute" before going to sleep, but by restarting cron you
  503. X * could then get it to execute a given minute's jobs more than once.
  504. X * instead we have the chance of missing a minute's jobs completely, but
  505. X * that's something sysadmin's know to expect what with crashing computers..
  506. X */
  507. static void
  508. cron_sync()
  509. X{
  510. X    extern struct tm    *localtime();
  511. X     register struct tm    *tm;
  512. X
  513. X    TargetTime = time((time_t*)0);
  514. X    tm = localtime(&TargetTime);
  515. X    TargetTime += (60 - tm->tm_sec);
  516. X}
  517. X
  518. X
  519. static void
  520. cron_sleep()
  521. X{
  522. X    extern void    do_command();
  523. X    extern int    job_runqueue();
  524. X    register int    seconds_to_wait;
  525. X
  526. X    do {
  527. X        seconds_to_wait = (int) (TargetTime - time((time_t*)0));
  528. X        Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n",
  529. X            getpid(), TargetTime, seconds_to_wait))
  530. X
  531. X        /* if we intend to sleep, this means that it's finally
  532. X         * time to empty the job queue (execute it).
  533. X         *
  534. X         * if we run any jobs, we'll probably screw up our timing,
  535. X         * so go recompute.
  536. X         *
  537. X         * note that we depend here on the left-to-right nature
  538. X         * of &&, and the short-circuiting.
  539. X         */
  540. X    } while (seconds_to_wait > 0 && job_runqueue());
  541. X
  542. X    if (seconds_to_wait > 0)
  543. X    {
  544. X        Debug(DSCH, ("[%d] sleeping for %d seconds\n",
  545. X            getpid(), seconds_to_wait))
  546. X        (void) sleep((unsigned int) seconds_to_wait);
  547. X    }
  548. X}
  549. X
  550. X
  551. X#if defined(BSD)
  552. static void
  553. sigchld_handler()
  554. X{
  555. X    union wait    waiter;
  556. X    int        pid;
  557. X
  558. X    for (;;)
  559. X    {
  560. X        pid = wait3(&waiter, WNOHANG, (struct rusage *)0);
  561. X        switch (pid)
  562. X        {
  563. X        case -1:
  564. X            Debug(DPROC,
  565. X                ("[%d] sigchld...no children\n", getpid()))
  566. X            return;
  567. X        case 0:
  568. X            Debug(DPROC,
  569. X                ("[%d] sigchld...no dead kids\n", getpid()))
  570. X            return;
  571. X        default:
  572. X            Debug(DPROC,
  573. X                ("[%d] sigchld...pid #%d died, stat=%d\n",
  574. X                getpid(), pid, waiter.w_status))
  575. X        }
  576. X    }
  577. X}
  578. X#endif /*BSD*/
  579. X
  580. X
  581. static void
  582. parse_args(argc, argv)
  583. X    int    argc;
  584. X    char    *argv[];
  585. X{
  586. X    extern    int    optind, getopt();
  587. X    extern    void    usage();
  588. X    extern    char    *optarg;
  589. X
  590. X    int    argch;
  591. X
  592. X    while (EOF != (argch = getopt(argc, argv, "x:")))
  593. X    {
  594. X        switch (argch)
  595. X        {
  596. X        default:
  597. X            usage();
  598. X        case 'x':
  599. X            if (!set_debug_flags(optarg))
  600. X                usage();
  601. X            break;
  602. X        }
  603. X    }
  604. X}
  605. END_OF_FILE
  606. if test 7067 -ne `wc -c <'crond.c'`; then
  607.     echo shar: \"'crond.c'\" unpacked with wrong size!
  608. fi
  609. # end of 'crond.c'
  610. fi
  611. if test -f 'crontab.5' -a "${1}" != "-c" ; then 
  612.   echo shar: Will not clobber existing file \"'crontab.5'\"
  613. else
  614. echo shar: Extracting \"'crontab.5'\" \(7270 characters\)
  615. sed "s/^X//" >'crontab.5' <<'END_OF_FILE'
  616. X.\" $Header: crontab.5,v 2.1 90/07/18 00:23:50 vixie Exp $
  617. X.\" 
  618. X.\"/* Copyright 1988,1990 by Paul Vixie
  619. X.\" * All rights reserved
  620. X.\" *
  621. X.\" * Distribute freely, except: don't remove my name from the source or
  622. X.\" * documentation (don't take credit for my work), mark your changes (don't
  623. X.\" * get me blamed for your possible bugs), don't alter or remove this
  624. X.\" * notice.  May be sold if buildable source is provided to buyer.  No
  625. X.\" * warrantee of any kind, express or implied, is included with this
  626. X.\" * software; use at your own risk, responsibility for damages (if any) to
  627. X.\" * anyone resulting from the use of this software rests entirely with the
  628. X.\" * user.
  629. X.\" *
  630. X.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
  631. X.\" * I'll try to keep a version up to date.  I can be reached as follows:
  632. X.\" * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
  633. X.\" * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
  634. X.\" */
  635. X.TH CRONTAB 5 "15 January 1990"
  636. X.UC 4
  637. X.SH NAME
  638. crontab \- tables for driving cron
  639. X.SH DESCRIPTION
  640. A
  641. X.I crontab
  642. file contains instructions to the
  643. X.IR crond (8)
  644. daemon of the general form: ``run this command at this time on this date''.
  645. XEach user has their own crontab, and commands in any given crontab will be
  646. executed as the user who owns the crontab.  Uucp and News will usually have
  647. their own crontabs, eliminating the need for explicitly running
  648. X.IR su (1)
  649. as part of a cron command.
  650. X.PP
  651. Blank lines and leading spaces and tabs are ignored.  Lines whose first
  652. non-space character is a pound-sign (#) are comments, and are ignored.
  653. Note that comments are not allowed on the same line as cron commands, since
  654. they will be taken to be part of the command.  Similarly, comments are not
  655. allowed on the same line as environment variable settings.
  656. X.PP
  657. An active line in a crontab will be either an environment setting or a cron
  658. command.  An environment setting is of the form,
  659. X.PP
  660. X    name = value
  661. X.PP
  662. where the spaces around the equal-sign (=) are optional, and any subsequent
  663. non-leading spaces in
  664. X.I value
  665. will be part of the value assigned to
  666. X.IR name .
  667. The
  668. X.I value
  669. string may be placed in quotes (single or double, but matching) to preserve
  670. leading or trailing blanks.
  671. X.PP
  672. Several environment variables are set up
  673. automatically by the
  674. X.IR crond (8)
  675. daemon from the /etc/passwd line of the crontab's owner: USER, HOME, and SHELL.
  676. HOME and SHELL may be overridden by settings in the crontab; USER may not.
  677. X.PP
  678. X(Note: for UUCP, always set SHELL=/bin/sh, or
  679. X.IR crond (8)
  680. will cheerfully try to execute your commands using /usr/lib/uucp/uucico.)
  681. X.PP
  682. X(Another note: the USER variable is sometimes called LOGNAME or worse on
  683. System V... on these systems, LOGNAME will be set rather than USER.)
  684. X.PP
  685. In addition to USER, HOME, and SHELL,
  686. X.IR crond (8)
  687. will look at MAILTO if it has any reason to send mail as a result of running
  688. commands in ``this'' crontab.  If MAILTO is defined (and non-empty), mail is
  689. sent to the user so named.  If MAILTO is defined but empty (MAILTO=""), no
  690. mail will be sent.  Otherwise mail is sent to the owner of the crontab.  This
  691. option is useful if you decide on /bin/mail instead of /usr/lib/sendmail as
  692. your mailer when you install cron -- /bin/mail doesn't do aliasing, and UUCP
  693. usually doesn't read its mail.
  694. X.PP
  695. The format of a cron command is very much the V7 standard, with a number of
  696. upward-compatible extensions.  Each line has five time and date fields,
  697. followed by a command.  Commands are executed by
  698. X.IR crond (8)
  699. when the minute, hour, and month of year fields match the current time,
  700. X.I and
  701. when at least one of the two day fields (day of month, or day of week)
  702. match the current time (see ``Note'' below).
  703. X.IR crond (8)
  704. examines cron entries once every minute.
  705. The time and date fields are:
  706. X.IP
  707. X.ta 1.5i
  708. field    allowed values
  709. X.br
  710. X-----    --------------
  711. X.br
  712. minute    0-59
  713. X.br
  714. hour    0-23
  715. X.br
  716. day of month    0-31
  717. X.br
  718. month    0-12 (or names, see below)
  719. X.br
  720. day of week    0-7 (0 or 7 is Sun, or use names)
  721. X.br
  722. X.PP
  723. A field may be an asterisk (*), which always matches the
  724. current time.
  725. X.PP
  726. Ranges of numbers are allowed.  Ranges are two numbers separated
  727. with a hyphen.  The specified range is inclusive.  For example,
  728. X8-11 for an ``hours'' entry specifies execution at hours 8, 9, 10
  729. and 11.
  730. X.PP
  731. Lists are allowed.  A list is a set of numbers (or ranges)
  732. separated by commas.  Examples: ``1,2,5,9'', ``0-4,8-12''.
  733. X.PP
  734. Step values can be used in conjunction with ranges.  Following
  735. a range with ``/<number>'' specifies skips of the number's value
  736. through the range.  For example, ``0-23/2'' can be used in the hours
  737. field to specify command execution every other hour (the alternative
  738. in the V7 standard is ``0,2,4,6,8,10,12,14,16,18,20,22'').
  739. X.PP
  740. Names can also be used for the ``month'' and ``day of week''
  741. fields.  Use the first three letters of the particular
  742. day or month (case doesn't matter).  Ranges or
  743. lists of names are not allowed.
  744. X.PP
  745. The ``sixth'' field (the rest of the line) specifies the command to be
  746. run.
  747. The entire command portion of the line, up to a newline or %
  748. character, will be executed by the user's login shell or by the shell
  749. specified in the SHELL variable of the cronfile.
  750. Percent-signs (%) in the command, unless escaped with backslash
  751. X(\\), will be changed into newline characters, and all data
  752. after the first % will be sent to the command as standard
  753. input.
  754. X.PP
  755. Note: The day of a command's execution can be specified by two
  756. fields \(em day of month, and day of week.  If both fields are
  757. restricted (ie, aren't *), the command will be run when
  758. X.I either
  759. field matches the current time.  For example,
  760. X.br
  761. X``30 4 1,15 * 5''
  762. would cause a command to be run at 4:30 am on the 1st and 15th of each
  763. month, plus every Friday.
  764. X.SH EXAMPLE CRON FILE
  765. X.nf
  766. X
  767. X# use /bin/sh to run commands, no matter what /etc/passwd says
  768. SHELL=/bin/sh
  769. X# mail any output to `paul', no matter whose crontab this is
  770. MAILTO=paul
  771. X#
  772. X# run five minutes after midnight, every day
  773. X5 0 * * *       $HOME/bin/daily.job >> $HOME/tmp/out 2>&1
  774. X# run at 2:15pm on the first of every month -- output mailed to paul
  775. X15 14 1 * *     $HOME/bin/monthly
  776. X# run at 10 pm on weekdays, annoy Joe
  777. X0 22 * * 1-5    mail -s "It's 10pm" joe%Joe,%%Where are your kids?%
  778. X23 0-23/2 * * * echo "run 23 minutes after midn, 2am, 4am ..., everyday"
  779. X5 4 * * sun     echo "run at 5 after 4 every sunday"
  780. X.fi
  781. X.SH SEE ALSO
  782. crond(8), crontab(1)
  783. X.SH EXTENSIONS
  784. When specifying day of week, both day 0 and day 7 will be considered Sunday.
  785. BSD and ATT seem to disagree about this.
  786. X.PP
  787. Lists and ranges are allowed to co-exist in the same field.  "1-3,7-9" would
  788. be rejected by ATT or BSD cron -- they want to see "1-3" or "7,8,9" ONLY.
  789. X.PP
  790. Ranges can include "steps", so "1-9/2" is the same as "1,3,5,7,9".
  791. X.PP
  792. Names of months or days of the week can be specified by name.
  793. X.PP
  794. XEnvironment variables can be set in the crontab.  In BSD or ATT, the
  795. environment handed to child processes is basically the one from /etc/rc.
  796. X.PP
  797. Command output is mailed to the crontab owner (BSD can't do this), can be
  798. mailed to a person other than the crontab owner (SysV can't do this), or the
  799. feature can be turned off and no mail will be sent at all (SysV can't do this
  800. either).
  801. X.SH AUTHOR
  802. X.nf
  803. Paul Vixie, paul@vixie.sf.ca.us
  804. END_OF_FILE
  805. if test 7270 -ne `wc -c <'crontab.5'`; then
  806.     echo shar: \"'crontab.5'\" unpacked with wrong size!
  807. fi
  808. # end of 'crontab.5'
  809. fi
  810. if test -f 'crontab.c' -a "${1}" != "-c" ; then 
  811.   echo shar: Will not clobber existing file \"'crontab.c'\"
  812. else
  813. echo shar: Extracting \"'crontab.c'\" \(8715 characters\)
  814. sed "s/^X//" >'crontab.c' <<'END_OF_FILE'
  815. X#if !defined(lint) && !defined(LINT)
  816. static char rcsid[] = "$Header: crontab.c,v 2.2 90/07/18 00:23:56 vixie Exp $";
  817. X#endif
  818. X
  819. X/* Revision 1.5  87/05/02  17:33:22  paul
  820. X * pokecron?  (RCS file has the rest of the log)
  821. X * 
  822. X * Revision 1.5  87/05/02  17:33:22  paul
  823. X * baseline for mod.sources release
  824. X * 
  825. X * Revision 1.4  87/03/31  13:11:48  paul
  826. X * I won't say that rs@mirror gave me this idea but crontab uses getopt() now
  827. X * 
  828. X * Revision 1.3  87/03/30  23:43:48  paul
  829. X * another suggestion from rs@mirror:
  830. X *   use getpwuid(getuid)->pw_name instead of getenv("USER")
  831. X *   this is a boost to security...
  832. X * 
  833. X * Revision 1.2  87/02/11  17:40:12  paul
  834. X * changed command syntax to allow append and replace instead of append as
  835. X * default and no replace at all.
  836. X * 
  837. X * Revision 1.1  87/01/26  23:49:06  paul
  838. X * Initial revision
  839. X */
  840. X
  841. X/* Copyright 1988,1990 by Paul Vixie
  842. X * All rights reserved
  843. X *
  844. X * Distribute freely, except: don't remove my name from the source or
  845. X * documentation (don't take credit for my work), mark your changes (don't
  846. X * get me blamed for your possible bugs), don't alter or remove this
  847. X * notice.  May be sold if buildable source is provided to buyer.  No
  848. X * warrantee of any kind, express or implied, is included with this
  849. X * software; use at your own risk, responsibility for damages (if any) to
  850. X * anyone resulting from the use of this software rests entirely with the
  851. X * user.
  852. X *
  853. X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
  854. X * I'll try to keep a version up to date.  I can be reached as follows:
  855. X * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
  856. X * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
  857. X */
  858. X
  859. X
  860. X#define    MAIN_PROGRAM
  861. X
  862. X
  863. X#include "cron.h"
  864. X#include <pwd.h>
  865. X#include <errno.h>
  866. X#include <sys/file.h>
  867. X#if defined(BSD)
  868. X# include <sys/time.h>
  869. X#endif  /*BSD*/
  870. X
  871. extern    char    *sprintf();
  872. X
  873. X
  874. static int    Pid;
  875. static char    User[MAX_UNAME], RealUser[MAX_UNAME];
  876. static char    Filename[MAX_FNAME];
  877. static FILE    *NewCrontab;
  878. static int    CheckErrorCount;
  879. static enum    {opt_unknown, opt_list, opt_delete, opt_replace}
  880. X        Option;
  881. X
  882. extern void    log_it();
  883. X
  884. X#if DEBUGGING
  885. static char    *Options[] = {"???", "list", "delete", "replace"};
  886. X#endif
  887. X
  888. static void
  889. usage()
  890. X{
  891. X    fprintf(stderr, "usage:  %s [-u user] ...\n", ProgramName);
  892. X    fprintf(stderr, " ... -l         (list user's crontab)\n");
  893. X    fprintf(stderr, " ... -d         (delete user's crontab)\n");
  894. X    fprintf(stderr, " ... -r file    (replace user's crontab)\n");
  895. X    exit(ERROR_EXIT);
  896. X}
  897. X
  898. X
  899. main(argc, argv)
  900. X    int    argc;
  901. X    char    *argv[];
  902. X{
  903. X    void    parse_args(), set_cron_uid(), set_cron_cwd(),
  904. X        list_cmd(), delete_cmd(), replace_cmd();
  905. X
  906. X    Pid = getpid();
  907. X    ProgramName = argv[0];
  908. X#if defined(BSD)
  909. X    setlinebuf(stderr);
  910. X#endif
  911. X    parse_args(argc, argv);        /* sets many globals, opens a file */
  912. X    set_cron_uid();
  913. X    set_cron_cwd();
  914. X    if (!allowed(User)) {
  915. X        fprintf(stderr,
  916. X            "You (%s) are not allowed to use this program (%s)\n",
  917. X            User, ProgramName);
  918. X        fprintf(stderr, "See crontab(1) for more information\n");
  919. X        log_it(RealUser, Pid, "AUTH", "crontab command not allowed");
  920. X        exit(ERROR_EXIT);
  921. X    }
  922. X    switch (Option)
  923. X    {
  924. X    case opt_list:        list_cmd();
  925. X                break;
  926. X    case opt_delete:    delete_cmd();
  927. X                break;
  928. X    case opt_replace:    replace_cmd();
  929. X                break;
  930. X    }
  931. X}
  932. X    
  933. X
  934. static void
  935. parse_args(argc, argv)
  936. X    int    argc;
  937. X    char    *argv[];
  938. X{
  939. X    void        usage();
  940. X    char        *getenv(), *strcpy();
  941. X    int        getuid();
  942. X    struct passwd    *getpwnam();
  943. X    extern int    getopt(), optind;
  944. X    extern char    *optarg;
  945. X
  946. X    struct passwd    *pw;
  947. X    int        argch;
  948. X
  949. X    if (!(pw = getpwuid(getuid())))
  950. X    {
  951. X        fprintf(stderr, "%s: your UID isn't in the passwd file.\n",
  952. X            ProgramName);
  953. X        fprintf(stderr, "bailing out.\n");
  954. X        exit(ERROR_EXIT);
  955. X    }
  956. X    strcpy(User, pw->pw_name);
  957. X    strcpy(RealUser, User);
  958. X    Filename[0] = '\0';
  959. X    Option = opt_unknown;
  960. X    while (EOF != (argch = getopt(argc, argv, "u:ldr:x:")))
  961. X    {
  962. X        switch (argch)
  963. X        {
  964. X        case 'x':
  965. X            if (!set_debug_flags(optarg))
  966. X                usage();
  967. X            break;
  968. X        case 'u':
  969. X            if (getuid() != ROOT_UID)
  970. X            {
  971. X                fprintf(stderr,
  972. X                    "must be privileged to use -u\n");
  973. X                exit(ERROR_EXIT);
  974. X            }
  975. X            if ((struct passwd *)NULL == getpwnam(optarg))
  976. X            {
  977. X                fprintf(stderr, "%s:  user `%s' unknown\n",
  978. X                    ProgramName, optarg);
  979. X                exit(ERROR_EXIT);
  980. X            }
  981. X            (void) strcpy(User, optarg);
  982. X            break;
  983. X        case 'l':
  984. X            if (Option != opt_unknown)
  985. X                usage();
  986. X            Option = opt_list;
  987. X            break;
  988. X        case 'd':
  989. X            if (Option != opt_unknown)
  990. X                usage();
  991. X            Option = opt_delete;
  992. X            break;
  993. X        case 'r':
  994. X            if (Option != opt_unknown)
  995. X                usage();
  996. X            Option = opt_replace;
  997. X            (void) strcpy(Filename, optarg);
  998. X            break;
  999. X        default:
  1000. X            usage();
  1001. X        }
  1002. X    }
  1003. X
  1004. X    endpwent();
  1005. X
  1006. X    if (Option == opt_unknown || argv[optind] != NULL)
  1007. X        usage();
  1008. X
  1009. X    if (Option == opt_replace) {
  1010. X        if (!Filename[0]) {
  1011. X            /* getopt(3) says this can't be true
  1012. X             * but I'm paranoid today.
  1013. X             */
  1014. X            fprintf(stderr, "filename must be given for -a or -r\n");
  1015. X            usage();
  1016. X        }
  1017. X        /* we have to open the file here because we're going to
  1018. X         * chdir(2) into /var/cron before we get around to
  1019. X         * reading the file.
  1020. X         */
  1021. X        if (!strcmp(Filename, "-")) {
  1022. X            NewCrontab = stdin;
  1023. X        } else {
  1024. X            if (!(NewCrontab = fopen(Filename, "r"))) {
  1025. X                perror(Filename);
  1026. X                exit(ERROR_EXIT);
  1027. X            }
  1028. X        }
  1029. X    }
  1030. X
  1031. X    Debug(DMISC, ("user=%s, file=%s, option=%s\n",
  1032. X                    User, Filename, Options[(int)Option]))
  1033. X}
  1034. X
  1035. X
  1036. static void
  1037. list_cmd()
  1038. X{
  1039. X    extern    errno;
  1040. X    char    n[MAX_FNAME];
  1041. X    FILE    *f;
  1042. X    int    ch;
  1043. X
  1044. X    log_it(RealUser, Pid, "LIST", User);
  1045. X    (void) sprintf(n, CRON_TAB(User));
  1046. X    if (!(f = fopen(n, "r")))
  1047. X    {
  1048. X        if (errno == ENOENT)
  1049. X            fprintf(stderr, "no crontab for %s\n", User);
  1050. X        else
  1051. X            perror(n);
  1052. X        exit(ERROR_EXIT);
  1053. X    }
  1054. X
  1055. X    /* file is open. copy to stdout, close.
  1056. X     */
  1057. X    Set_LineNum(1)
  1058. X    while (EOF != (ch = get_char(f)))
  1059. X        putchar(ch);
  1060. X    fclose(f);
  1061. X}
  1062. X
  1063. X
  1064. static void
  1065. delete_cmd()
  1066. X{
  1067. X    extern    errno;
  1068. X    int    unlink();
  1069. X    void    poke_daemon();
  1070. X    char    n[MAX_FNAME];
  1071. X
  1072. X    log_it(RealUser, Pid, "DELETE", User);
  1073. X    (void) sprintf(n, CRON_TAB(User));
  1074. X    if (unlink(n))
  1075. X    {
  1076. X        if (errno == ENOENT)
  1077. X            fprintf(stderr, "no crontab for %s\n", User);
  1078. X        else
  1079. X            perror(n);
  1080. X        exit(ERROR_EXIT);
  1081. X    }
  1082. X    poke_daemon();
  1083. X}
  1084. X
  1085. X
  1086. static void
  1087. check_error(msg)
  1088. X    char    *msg;
  1089. X{
  1090. X    CheckErrorCount += 1;
  1091. X    fprintf(stderr, "\"%s\", line %d: %s\n", Filename, LineNumber, msg);
  1092. X}
  1093. X
  1094. X
  1095. static void
  1096. replace_cmd()
  1097. X{
  1098. X    char    *sprintf();
  1099. X    entry    *load_entry();
  1100. X    int    load_env();
  1101. X    int    unlink();
  1102. X    void    free_entry();
  1103. X    void    check_error();
  1104. X    void    poke_daemon();
  1105. X    extern    errno;
  1106. X
  1107. X    char    n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME];
  1108. X    FILE    *tmp;
  1109. X    int    ch;
  1110. X    entry    *e;
  1111. X    int    status;
  1112. X    time_t    now = time(NULL);
  1113. X
  1114. X    (void) sprintf(n, "tmp.%d", Pid);
  1115. X    (void) sprintf(tn, CRON_TAB(n));
  1116. X    if (!(tmp = fopen(tn, "w"))) {
  1117. X        perror(tn);
  1118. X        exit(ERROR_EXIT);
  1119. X    }
  1120. X
  1121. X    /* write a signature at the top of the file.  for brian.
  1122. X     */
  1123. X    fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now));
  1124. X    fprintf(tmp, "# (Cron version -- %s)\n", rcsid);
  1125. X
  1126. X    /* copy the crontab to the tmp
  1127. X     */
  1128. X    Set_LineNum(1)
  1129. X    while (EOF != (ch = get_char(NewCrontab)))
  1130. X        putc(ch, tmp);
  1131. X    fclose(NewCrontab);
  1132. X    fflush(tmp);  rewind(tmp);
  1133. X
  1134. X    if (ferror(tmp)) {
  1135. X        fprintf("%s: error while writing new crontab to %s\n",
  1136. X            ProgramName, tn);
  1137. X        fclose(tmp);  unlink(tn);
  1138. X        exit(ERROR_EXIT);
  1139. X    }
  1140. X
  1141. X    /* check the syntax of the file being installed.
  1142. X     */
  1143. X
  1144. X    /* BUG: was reporting errors after the EOF if there were any errors
  1145. X     * in the file proper -- kludged it by stopping after first error.
  1146. X     *        vix 31mar87
  1147. X     */
  1148. X    CheckErrorCount = 0;
  1149. X    while (!CheckErrorCount && (status = load_env(envstr, tmp)) >= OK)
  1150. X    {
  1151. X        if (status == FALSE)
  1152. X        {
  1153. X            if (NULL != (e = load_entry(NewCrontab, check_error)))
  1154. X                free((char *) e);
  1155. X        }
  1156. X    }
  1157. X
  1158. X    if (CheckErrorCount != 0)
  1159. X    {
  1160. X        fprintf(stderr, "errors in crontab file, can't install.\n");
  1161. X        fclose(tmp);  unlink(tn);
  1162. X        exit(ERROR_EXIT);
  1163. X    }
  1164. X
  1165. X    if (fchown(fileno(tmp), ROOT_UID, -1) < OK)
  1166. X    {
  1167. X        perror("chown");
  1168. X        fclose(tmp);  unlink(tn);
  1169. X        exit(ERROR_EXIT);
  1170. X    }
  1171. X
  1172. X    if (fchmod(fileno(tmp), 0600) < OK)
  1173. X    {
  1174. X        perror("chown");
  1175. X        fclose(tmp);  unlink(tn);
  1176. X        exit(ERROR_EXIT);
  1177. X    }
  1178. X
  1179. X    if (fclose(tmp) == EOF) {
  1180. X        perror("fclose");
  1181. X        unlink(tn);
  1182. X        exit(ERROR_EXIT);
  1183. X    }
  1184. X
  1185. X    (void) sprintf(n, CRON_TAB(User));
  1186. X    if (rename(tn, n))
  1187. X    {
  1188. X        fprintf(stderr, "%s: error renaming %s to %s\n",
  1189. X            ProgramName, tn, n);
  1190. X        perror("rename");
  1191. X        unlink(tn);
  1192. X        exit(ERROR_EXIT);
  1193. X    }
  1194. X    log_it(RealUser, Pid, "REPLACE", User);
  1195. X
  1196. X    poke_daemon();
  1197. X}
  1198. X
  1199. X
  1200. static void
  1201. poke_daemon()
  1202. X{
  1203. X#if defined(BSD)
  1204. X    struct timeval tvs[2];
  1205. X    struct timezone tz;
  1206. X
  1207. X    (void) gettimeofday(&tvs[0], &tz);
  1208. X    tvs[1] = tvs[0];
  1209. X    if (utimes(SPOOL_DIR, tvs) < OK)
  1210. X    {
  1211. X        fprintf(stderr, "crontab: can't update mtime on spooldir\n");
  1212. X        perror(SPOOL_DIR);
  1213. X        return;
  1214. X    }
  1215. X#endif  /*BSD*/
  1216. X#if defined(ATT)
  1217. X    if (utime(SPOOL_DIR, NULL) < OK)
  1218. X    {
  1219. X        fprintf(stderr, "crontab: can't update mtime on spooldir\n");
  1220. X        perror(SPOOL_DIR);
  1221. X        return;
  1222. X    }
  1223. X#endif  /*ATT*/
  1224. X}
  1225. END_OF_FILE
  1226. if test 8715 -ne `wc -c <'crontab.c'`; then
  1227.     echo shar: \"'crontab.c'\" unpacked with wrong size!
  1228. fi
  1229. # end of 'crontab.c'
  1230. fi
  1231. if test -f 'database.c' -a "${1}" != "-c" ; then 
  1232.   echo shar: Will not clobber existing file \"'database.c'\"
  1233. else
  1234. echo shar: Extracting \"'database.c'\" \(6768 characters\)
  1235. sed "s/^X//" >'database.c' <<'END_OF_FILE'
  1236. X#if !defined(lint) && !defined(LINT)
  1237. static char rcsid[] = "$Header: database.c,v 2.1 90/07/18 00:23:51 vixie Exp $";
  1238. X#endif
  1239. X
  1240. X/* vix 26jan87 [RCS has the log]
  1241. X */
  1242. X
  1243. X/* Copyright 1988,1990 by Paul Vixie
  1244. X * All rights reserved
  1245. X *
  1246. X * Distribute freely, except: don't remove my name from the source or
  1247. X * documentation (don't take credit for my work), mark your changes (don't
  1248. X * get me blamed for your possible bugs), don't alter or remove this
  1249. X * notice.  May be sold if buildable source is provided to buyer.  No
  1250. X * warrantee of any kind, express or implied, is included with this
  1251. X * software; use at your own risk, responsibility for damages (if any) to
  1252. X * anyone resulting from the use of this software rests entirely with the
  1253. X * user.
  1254. X *
  1255. X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
  1256. X * I'll try to keep a version up to date.  I can be reached as follows:
  1257. X * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
  1258. X * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
  1259. X */
  1260. X
  1261. X
  1262. X#include "cron.h"
  1263. X#include <pwd.h>
  1264. X#if defined(BSD)
  1265. X# include <sys/file.h>
  1266. X# include <sys/dir.h>
  1267. X#endif
  1268. X#if defined(ATT)
  1269. X# include <sys/file.h>
  1270. X# include <ndir.h>
  1271. X# include <fcntl.h>
  1272. X#endif
  1273. X
  1274. X
  1275. extern void    perror(), exit();
  1276. X
  1277. X
  1278. void
  1279. load_database(old_db)
  1280. X    cron_db        *old_db;
  1281. X{
  1282. X    extern void    link_user(), unlink_user(), free_user();
  1283. X    extern user    *load_user(), *find_user();
  1284. X    extern char    *env_get();
  1285. X
  1286. X    static DIR    *dir = NULL;
  1287. X
  1288. X    struct stat    statbuf;
  1289. X    struct direct    *dp;
  1290. X    cron_db        new_db;
  1291. X    user        *u;
  1292. X
  1293. X    Debug(DLOAD, ("[%d] load_database()\n", getpid()))
  1294. X
  1295. X    /* before we start loading any data, do a stat on SPOOL_DIR
  1296. X     * so that if anything changes as of this moment (i.e., before we've
  1297. X     * cached any of the database), we'll see the changes next time.
  1298. X     */
  1299. X    if (stat(SPOOL_DIR, &statbuf) < OK)
  1300. X    {
  1301. X        log_it("CROND", getpid(), "STAT FAILED", SPOOL_DIR);
  1302. X        (void) exit(ERROR_EXIT);
  1303. X    }
  1304. X
  1305. X    /* if spooldir's mtime has not changed, we don't need to fiddle with
  1306. X     * the database.  Note that if /etc/passwd changes (like, someone's
  1307. X     * UID/GID/HOME/SHELL, we won't see it.  Maybe we should
  1308. X     * keep an mtime for the passwd file?  HINT
  1309. X     *
  1310. X     * Note that old_db->mtime is initialized to 0 in main(), and
  1311. X     * so is guaranteed to be different than the stat() mtime the first
  1312. X     * time this function is called.
  1313. X     */
  1314. X    if (old_db->mtime == statbuf.st_mtime)
  1315. X    {
  1316. X        Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n",
  1317. X            getpid()))
  1318. X        return;
  1319. X    }
  1320. X
  1321. X    /* make sure the dir is open.  only happens the first time, since
  1322. X     * the DIR is static and we don't close it.  Rewind the dir.
  1323. X     */
  1324. X    if (dir == NULL)
  1325. X    {
  1326. X        if (!(dir = opendir(SPOOL_DIR)))
  1327. X        {
  1328. X            log_it("CROND", getpid(), "OPENDIR FAILED", SPOOL_DIR);
  1329. X            (void) exit(ERROR_EXIT);
  1330. X        }
  1331. X    }
  1332. X    (void) rewinddir(dir);
  1333. X
  1334. X    /* something's different.  make a new database, moving unchanged
  1335. X     * elements from the old database, reloading elements that have
  1336. X     * actually changed.  Whatever is left in the old database when
  1337. X     * we're done is chaff -- crontabs that disappeared.
  1338. X     */
  1339. X    new_db.mtime = statbuf.st_mtime;
  1340. X    new_db.head = new_db.tail = NULL;
  1341. X
  1342. X    while (NULL != (dp = readdir(dir)))
  1343. X    {
  1344. X        extern struct passwd    *getpwnam();
  1345. X        struct passwd        *pw;
  1346. X        int            crontab_fd;
  1347. X        char            fname[MAXNAMLEN+1],
  1348. X                    tabname[MAXNAMLEN+1];
  1349. X
  1350. X        (void) strncpy(fname, dp->d_name, (int) dp->d_namlen);
  1351. X        fname[dp->d_namlen] = '\0';
  1352. X
  1353. X        /* avoid file names beginning with ".".  this is good
  1354. X         * because we would otherwise waste two guaranteed calls
  1355. X         * to getpwnam() for . and .., and also because user names
  1356. X         * starting with a period are just too nasty to consider.
  1357. X         */
  1358. X        if (fname[0] == '.')
  1359. X            goto next_crontab;
  1360. X
  1361. X        if (NULL == (pw = getpwnam(fname)))
  1362. X        {
  1363. X            /* file doesn't have a user in passwd file.
  1364. X             */
  1365. X            log_it(fname, getpid(), "ORPHAN", "no passwd entry");
  1366. X            goto next_crontab;
  1367. X        }
  1368. X
  1369. X        sprintf(tabname, CRON_TAB(fname));
  1370. X        if ((crontab_fd = open(tabname, O_RDONLY, 0)) < OK)
  1371. X        {
  1372. X            /* crontab not accessible?
  1373. X             */
  1374. X            log_it(fname, getpid(), "CAN'T OPEN", tabname);
  1375. X            goto next_crontab;
  1376. X        }
  1377. X
  1378. X        if (fstat(crontab_fd, &statbuf) < OK)
  1379. X        {
  1380. X            log_it(fname, getpid(), "FSTAT FAILED", tabname);
  1381. X            goto next_crontab;
  1382. X        }
  1383. X
  1384. X        Debug(DLOAD, ("\t%s:", fname))
  1385. X        u = find_user(old_db, fname);
  1386. X        if (u != NULL)
  1387. X        {
  1388. X            /* if crontab has not changed since we last read it
  1389. X             * in, then we can just use our existing entry.
  1390. X             * note that we do not check for changes in the
  1391. X             * passwd entry (uid, home dir, etc).  HINT
  1392. X             */
  1393. X            if (u->mtime == statbuf.st_mtime)
  1394. X            {
  1395. X                Debug(DLOAD, (" [no change, using old data]"))
  1396. X                unlink_user(old_db, u);
  1397. X                link_user(&new_db, u);
  1398. X                goto next_crontab;
  1399. X            }
  1400. X
  1401. X            /* before we fall through to the code that will reload
  1402. X             * the user, let's deallocate and unlink the user in
  1403. X             * the old database.  This is more a point of memory
  1404. X             * efficiency than anything else, since all leftover
  1405. X             * users will be deleted from the old database when
  1406. X             * we finish with the crontab...
  1407. X             */
  1408. X            Debug(DLOAD, (" [delete old data]"))
  1409. X            unlink_user(old_db, u);
  1410. X            free_user(u);
  1411. X        }
  1412. X        u = load_user(
  1413. X            crontab_fd,
  1414. X            pw->pw_name,
  1415. X            pw->pw_uid,
  1416. X            pw->pw_gid,
  1417. X            pw->pw_dir,
  1418. X            pw->pw_shell
  1419. X        );
  1420. X        if (u != NULL)
  1421. X        {
  1422. X            u->mtime = statbuf.st_mtime;
  1423. X            link_user(&new_db, u);
  1424. X        }
  1425. next_crontab:
  1426. X        if (crontab_fd >= OK) {
  1427. X            Debug(DLOAD, (" [done]\n"))
  1428. X            close(crontab_fd);
  1429. X        }
  1430. X    }
  1431. X    /* if we don't do this, then when our children eventually call
  1432. X     * getpwnam() in do_command.c's child_process to verify MAILTO=,
  1433. X     * they will screw us up (and v-v).
  1434. X     *
  1435. X     * (this was lots of fun to find...)
  1436. X     */
  1437. X    endpwent();
  1438. X
  1439. X    /* whatever's left in the old database is now junk.
  1440. X     */
  1441. X    Debug(DLOAD, ("unlinking old database:\n"))
  1442. X    for (u = old_db->head;  u != NULL;  u = u->next)
  1443. X    {
  1444. X        Debug(DLOAD, ("\t%s\n", env_get(USERENV, u->envp)))
  1445. X        unlink_user(old_db, u);
  1446. X        free_user(u);
  1447. X    }
  1448. X
  1449. X    /* overwrite the database control block with the new one.
  1450. X     */
  1451. X    Debug(DLOAD, ("installing new database\n"))
  1452. X#if defined(BSD)
  1453. X    /* BSD has structure assignments */
  1454. X    *old_db = new_db;
  1455. X#endif
  1456. X#if defined(ATT)
  1457. X    /* ATT, well, I don't know.  Use memcpy(). */
  1458. X    memcpy(old_db, &new_db, sizeof(cron_db));
  1459. X#endif
  1460. X    Debug(DLOAD, ("load_database is done\n"))
  1461. X}
  1462. X
  1463. X
  1464. void
  1465. link_user(db, u)
  1466. X    cron_db    *db;
  1467. X    user    *u;
  1468. X{
  1469. X    if (db->head == NULL)
  1470. X        db->head = u;
  1471. X    if (db->tail)
  1472. X        db->tail->next = u;
  1473. X    u->prev = db->tail;
  1474. X    u->next = NULL;
  1475. X    db->tail = u;
  1476. X}
  1477. X
  1478. X
  1479. void
  1480. unlink_user(db, u)
  1481. X    cron_db    *db;
  1482. X    user    *u;
  1483. X{
  1484. X    if (u->prev == NULL)
  1485. X        db->head = u->next;
  1486. X    else
  1487. X        u->prev->next = u->next;
  1488. X
  1489. X    if (u->next == NULL)
  1490. X        db->tail = u->prev;
  1491. X    else
  1492. X        u->next->prev = u->prev;
  1493. X}
  1494. X
  1495. X
  1496. user *
  1497. find_user(db, name)
  1498. X    cron_db    *db;
  1499. X    char    *name;
  1500. X{
  1501. X    char    *env_get();
  1502. X    user    *u;
  1503. X
  1504. X    for (u = db->head;  u != NULL;  u = u->next)
  1505. X        if (!strcmp(env_get(USERENV, u->envp), name))
  1506. X            break;
  1507. X    return u;
  1508. X}
  1509. END_OF_FILE
  1510. if test 6768 -ne `wc -c <'database.c'`; then
  1511.     echo shar: \"'database.c'\" unpacked with wrong size!
  1512. fi
  1513. # end of 'database.c'
  1514. fi
  1515. if test -f 'entry.c' -a "${1}" != "-c" ; then 
  1516.   echo shar: Will not clobber existing file \"'entry.c'\"
  1517. else
  1518. echo shar: Extracting \"'entry.c'\" \(11561 characters\)
  1519. sed "s/^X//" >'entry.c' <<'END_OF_FILE'
  1520. X#if !defined(lint) && !defined(LINT)
  1521. static char rcsid[] = "$Header: entry.c,v 2.1 90/07/18 00:23:41 vixie Exp $";
  1522. X#endif
  1523. X
  1524. X/* vix 26jan87 [RCS'd; rest of log is in RCS file]
  1525. X * vix 01jan87 [added line-level error recovery]
  1526. X * vix 31dec86 [added /step to the from-to range, per bob@acornrc]
  1527. X * vix 30dec86 [written]
  1528. X */
  1529. X
  1530. X
  1531. X/* Copyright 1988,1990 by Paul Vixie
  1532. X * All rights reserved
  1533. X *
  1534. X * Distribute freely, except: don't remove my name from the source or
  1535. X * documentation (don't take credit for my work), mark your changes (don't
  1536. X * get me blamed for your possible bugs), don't alter or remove this
  1537. X * notice.  May be sold if buildable source is provided to buyer.  No
  1538. X * warrantee of any kind, express or implied, is included with this
  1539. X * software; use at your own risk, responsibility for damages (if any) to
  1540. X * anyone resulting from the use of this software rests entirely with the
  1541. X * user.
  1542. X *
  1543. X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
  1544. X * I'll try to keep a version up to date.  I can be reached as follows:
  1545. X * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
  1546. X * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
  1547. X */
  1548. X
  1549. X
  1550. X#include "cron.h"
  1551. X
  1552. typedef    enum
  1553. X    {e_none, e_minute, e_hour, e_dom, e_month, e_dow, e_cmd, e_timespec}
  1554. X    ecode_e;
  1555. static char *ecodes[] =
  1556. X    {
  1557. X        "no error",
  1558. X        "bad minute",
  1559. X        "bad hour",
  1560. X        "bad day-of-month",
  1561. X        "bad month",
  1562. X        "bad day-of-week",
  1563. X        "bad command",
  1564. X        "bad time specifier"
  1565. X    };
  1566. X
  1567. void
  1568. free_entry(e)
  1569. X    entry    *e;
  1570. X{
  1571. X    int    free();
  1572. X
  1573. X    (void) free(e->cmd);
  1574. X    (void) free(e);
  1575. X}
  1576. X
  1577. X
  1578. entry *
  1579. load_entry(file, error_func)
  1580. X    FILE    *file;
  1581. X    void    (*error_func)();
  1582. X{
  1583. X    /* this function reads one crontab entry -- the next -- from a file.
  1584. X     * it skips any leading blank lines, ignores comments, and returns
  1585. X     * EOF if for any reason the entry can't be read and parsed.
  1586. X     *
  1587. X     * the entry IS parsed here, btw.
  1588. X     *
  1589. X     * syntax:
  1590. X     *    minutes hours doms months dows cmd\n
  1591. X     */
  1592. X
  1593. X    extern int    free();
  1594. X    extern char    *malloc(), *savestr();
  1595. X    extern void    unget_char();
  1596. X    static char    get_list();
  1597. X
  1598. X    ecode_e    ecode = e_none;
  1599. X    entry    *e;
  1600. X    int    ch;
  1601. X    void    skip_comments();
  1602. X    char    cmd[MAX_COMMAND];
  1603. X
  1604. X    e = (entry *) calloc(sizeof(entry), sizeof(char));
  1605. X
  1606. X    Debug(DPARS, ("load_entry()...about to eat comments\n"))
  1607. X
  1608. X    skip_comments(file);
  1609. X
  1610. X    ch = get_char(file);
  1611. X
  1612. X    /* ch is now the first useful character of a useful line.
  1613. X     * it may be an @special or it may be the first character
  1614. X     * of a list of minutes.
  1615. X     */
  1616. X
  1617. X    if (ch == '@')
  1618. X    {
  1619. X        /* all of these should be flagged and load-limited; i.e.,
  1620. X         * instead of @hourly meaning "0 * * * *" it should mean
  1621. X         * "close to the front of every hour but not 'til the
  1622. X         * system load is low".  Problems are: how do you know
  1623. X         * what "low" means? (save me from /etc/crond.conf!) and:
  1624. X         * how to guarantee low variance (how low is low?), which
  1625. X         * means how to we run roughly every hour -- seems like
  1626. X         * we need to keep a history or let the first hour set
  1627. X         * the schedule, which means we aren't load-limited
  1628. X         * anymore.  too much for my overloaded brain. (vix, jan90)
  1629. X         * HINT
  1630. X         */
  1631. X        ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
  1632. X        if (!strcmp("reboot", cmd)) {
  1633. X            e->flags |= WHEN_REBOOT;
  1634. X        } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){
  1635. X            bit_set(e->minute, 0);
  1636. X            bit_set(e->hour, 0);
  1637. X            bit_set(e->dom, 0);
  1638. X            bit_set(e->month, 0);
  1639. X            bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
  1640. X        } else if (!strcmp("monthly", cmd)) {
  1641. X            bit_set(e->minute, 0);
  1642. X            bit_set(e->hour, 0);
  1643. X            bit_set(e->dom, 0);
  1644. X            bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
  1645. X            bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
  1646. X        } else if (!strcmp("weekly", cmd)) {
  1647. X            bit_set(e->minute, 0);
  1648. X            bit_set(e->hour, 0);
  1649. X            bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
  1650. X            bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
  1651. X            bit_set(e->dow, 0);
  1652. X        } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
  1653. X            bit_set(e->minute, 0);
  1654. X            bit_set(e->hour, 0);
  1655. X            bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
  1656. X            bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
  1657. X            bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
  1658. X        } else if (!strcmp("hourly", cmd)) {
  1659. X            bit_set(e->minute, 0);
  1660. X            bit_set(e->hour, (LAST_HOUR-FIRST_HOUR+1));
  1661. X            bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
  1662. X            bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
  1663. X            bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
  1664. X        } else {
  1665. X            ecode = e_timespec;
  1666. X            goto eof;
  1667. X        }
  1668. X    }
  1669. X    else
  1670. X    {
  1671. X        Debug(DPARS, ("load_entry()...about to parse numerics\n"))
  1672. X
  1673. X        ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
  1674. X                PPC_NULL, ch, file);
  1675. X        if (ch == EOF)
  1676. X        {
  1677. X            ecode = e_minute;
  1678. X            goto eof;
  1679. X        }
  1680. X
  1681. X        /* hours
  1682. X         */
  1683. X
  1684. X        ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
  1685. X                PPC_NULL, ch, file);
  1686. X        if (ch == EOF)
  1687. X        {
  1688. X            ecode = e_hour;
  1689. X            goto eof;
  1690. X        }
  1691. X
  1692. X        /* DOM (days of month)
  1693. X         */
  1694. X
  1695. X        if (ch == '*') e->flags |= DOM_STAR;
  1696. X        ch = get_list(e->dom, FIRST_DOM, LAST_DOM, PPC_NULL, ch, file);
  1697. X        if (ch == EOF)
  1698. X        {
  1699. X            ecode = e_dom;
  1700. X            goto eof;
  1701. X        }
  1702. X
  1703. X        /* month
  1704. X         */
  1705. X
  1706. X        ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
  1707. X                MonthNames, ch, file);
  1708. X        if (ch == EOF)
  1709. X        {
  1710. X            ecode = e_month;
  1711. X            goto eof;
  1712. X        }
  1713. X
  1714. X        /* DOW (days of week)
  1715. X         */
  1716. X
  1717. X        if (ch == '*') e->flags |= DOW_STAR;
  1718. X        ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
  1719. X                DowNames, ch, file);
  1720. X        if (ch == EOF)
  1721. X        {
  1722. X            ecode = e_dow;
  1723. X            goto eof;
  1724. X        }
  1725. X    }
  1726. X
  1727. X    /* make sundays equivilent */
  1728. X    if (bit_test(e->dow, 0) || bit_test(e->dow, 7))
  1729. X    {
  1730. X        bit_set(e->dow, 0);
  1731. X        bit_set(e->dow, 7);
  1732. X    }
  1733. X
  1734. X    Debug(DPARS, ("load_entry()...about to parse command\n"))
  1735. X
  1736. X    /* ch is first character of a command.  everything up to the next
  1737. X     * \n or EOF is part of the command... too bad we don't know in
  1738. X     * advance how long it will be, since we need to malloc a string
  1739. X     * for it... so, we limit it to MAX_COMMAND
  1740. X     */ 
  1741. X    unget_char(ch, file);
  1742. X    ch = get_string(cmd, MAX_COMMAND, file, "\n");
  1743. X
  1744. X    /* a file without a \n before the EOF is rude, so we'll complain...
  1745. X     */
  1746. X    if (ch == EOF)
  1747. X    {
  1748. X        ecode = e_cmd;
  1749. X        goto eof;
  1750. X    }
  1751. X
  1752. X    /* got the command in the 'cmd' string; save it in *e.
  1753. X     */
  1754. X    e->cmd = savestr(cmd);
  1755. X
  1756. X    Debug(DPARS, ("load_entry()...returning successfully\n"))
  1757. X
  1758. X    /* success, fini, return pointer to the entry we just created...
  1759. X     */
  1760. X    return e;
  1761. X
  1762. eof:    /* if we want to return EOF, we have to jump down here and
  1763. X     * free the entry we've been building.
  1764. X     *
  1765. X     * now, in some cases, a parse routine will have returned EOF to
  1766. X     * indicate an error, but the file is not actually done.  since, in
  1767. X     * that case, we only want to skip the line with the error on it,
  1768. X     * we'll do that here.
  1769. X     *
  1770. X     * many, including the author, see what's below as evil programming
  1771. X     * practice: since I didn't want to change the structure of this
  1772. X     * whole function to support this error recovery, I recurse.  Cursed!
  1773. X     * (At least it's tail-recursion, as if it matters in C - vix/8feb88)
  1774. X     * I'm seriously considering using (another) GOTO...   argh!
  1775. X     * (this does not get less disgusting over time.  vix/15nov88)
  1776. X     */
  1777. X
  1778. X    (void) free(e);
  1779. X
  1780. X    if (feof(file))
  1781. X        return NULL;
  1782. X
  1783. X    if (error_func)
  1784. X        (*error_func)(ecodes[(int)ecode]);
  1785. X    do  {ch = get_char(file);}
  1786. X    while (ch != EOF && ch != '\n');
  1787. X    if (ch == EOF)
  1788. X        return NULL;
  1789. X    return load_entry(file, error_func);
  1790. X}
  1791. X
  1792. X
  1793. static char
  1794. get_list(bits, low, high, names, ch, file)
  1795. X    bitstr_t    *bits;        /* one bit per flag, default=FALSE */
  1796. X    int        low, high;    /* bounds, impl. offset for bitstr */
  1797. X    char        *names[];    /* NULL or *[] of names for these elements */
  1798. X    int        ch;        /* current character being processed */
  1799. X    FILE        *file;        /* file being read */
  1800. X{
  1801. X    static char    get_range();
  1802. X    register int    done;
  1803. X
  1804. X    /* we know that we point to a non-blank character here;
  1805. X     * must do a Skip_Blanks before we exit, so that the
  1806. X     * next call (or the code that picks up the cmd) can
  1807. X     * assume the same thing.
  1808. X     */
  1809. X
  1810. X    Debug(DPARS|DEXT, ("get_list()...entered\n"))
  1811. X
  1812. X    /* list = "*" | range {"," range}
  1813. X     */
  1814. X    
  1815. X    if (ch == '*')
  1816. X    {
  1817. X        /* '*' means 'all elements'.
  1818. X         */
  1819. X        bit_nset(bits, 0, (high-low+1));
  1820. X        goto exit;
  1821. X    }
  1822. X
  1823. X    /* clear the bit string, since the default is 'off'.
  1824. X     */
  1825. X    bit_nclear(bits, 0, (high-low+1));
  1826. X
  1827. X    /* process all ranges
  1828. X     */
  1829. X    done = FALSE;
  1830. X    while (!done)
  1831. X    {
  1832. X        ch = get_range(bits, low, high, names, ch, file);
  1833. X        if (ch == ',')
  1834. X            ch = get_char(file);
  1835. X        else
  1836. X            done = TRUE;
  1837. X    }
  1838. X
  1839. exit:    /* exiting.  skip to some blanks, then skip over the blanks.
  1840. X     */
  1841. X    Skip_Nonblanks(ch, file)
  1842. X    Skip_Blanks(ch, file)
  1843. X
  1844. X    Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch))
  1845. X
  1846. X    return ch;
  1847. X}
  1848. X
  1849. X
  1850. static char
  1851. get_range(bits, low, high, names, ch, file)
  1852. X    bitstr_t    *bits;        /* one bit per flag, default=FALSE */
  1853. X    int        low, high;    /* bounds, impl. offset for bitstr */
  1854. X    char        *names[];    /* NULL or names of elements */
  1855. X    int        ch;        /* current character being processed */
  1856. X    FILE        *file;        /* file being read */
  1857. X{
  1858. X    /* range = number | number "-" number [ "/" number ]
  1859. X     */
  1860. X
  1861. X    static int    set_element();
  1862. X    static char    get_number();
  1863. X    register int    i;
  1864. X    auto int    num1, num2, num3;
  1865. X
  1866. X    Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"))
  1867. X
  1868. X    if (EOF == (ch = get_number(&num1, low, names, ch, file)))
  1869. X        return EOF;
  1870. X
  1871. X    if (ch != '-')
  1872. X    {
  1873. X        /* not a range, it's a single number.
  1874. X         */
  1875. X        if (EOF == set_element(bits, low, high, num1))
  1876. X            return EOF;
  1877. X    }
  1878. X    else
  1879. X    {
  1880. X        /* eat the dash
  1881. X         */
  1882. X        ch = get_char(file);
  1883. X        if (ch == EOF)
  1884. X            return EOF;
  1885. X
  1886. X        /* get the number following the dash
  1887. X         */
  1888. X        ch = get_number(&num2, low, names, ch, file);
  1889. X        if (ch == EOF)
  1890. X            return EOF;
  1891. X
  1892. X        /* check for step size
  1893. X         */
  1894. X        if (ch == '/')
  1895. X        {
  1896. X            /* eat the slash
  1897. X             */
  1898. X            ch = get_char(file);
  1899. X            if (ch == EOF)
  1900. X                return EOF;
  1901. X
  1902. X            /* get the step size -- note: we don't pass the
  1903. X             * names here, because the number is not an
  1904. X             * element id, it's a step size.  'low' is
  1905. X             * sent as a 0 since there is no offset either.
  1906. X             */
  1907. X            ch = get_number(&num3, 0, PPC_NULL, ch, file);
  1908. X            if (ch == EOF)
  1909. X                return EOF;
  1910. X        }
  1911. X        else
  1912. X        {
  1913. X            /* no step.  default==1.
  1914. X             */
  1915. X            num3 = 1;
  1916. X        }
  1917. X
  1918. X        /* range. set all elements from num1 to num2, stepping
  1919. X         * by num3.  (the step is a downward-compatible extension
  1920. X         * proposed conceptually by bob@acornrc, syntactically
  1921. X         * designed then implmented by paul vixie).
  1922. X         */
  1923. X        for (i = num1;  i <= num2;  i += num3)
  1924. X            if (EOF == set_element(bits, low, high, i))
  1925. X                return EOF;
  1926. X    }
  1927. X    return ch;
  1928. X}
  1929. X
  1930. X
  1931. static char
  1932. get_number(numptr, low, names, ch, file)
  1933. X    int    *numptr;
  1934. X    int    low;
  1935. X    char    *names[];
  1936. X    char    ch;
  1937. X    FILE    *file;
  1938. X{
  1939. X    char    temp[MAX_TEMPSTR], *pc;
  1940. X    int    len, i, all_digits;
  1941. X
  1942. X    /* collect alphanumerics into our fixed-size temp array
  1943. X     */
  1944. X    pc = temp;
  1945. X    len = 0;
  1946. X    all_digits = TRUE;
  1947. X    while (isalnum(ch))
  1948. X    {
  1949. X        if (++len >= MAX_TEMPSTR)
  1950. X            return EOF;
  1951. X
  1952. X        *pc++ = ch;
  1953. X
  1954. X        if (!isdigit(ch))
  1955. X            all_digits = FALSE;
  1956. X
  1957. X        ch = get_char(file);
  1958. X    }
  1959. X    *pc = '\0';
  1960. X
  1961. X    /* try to find the name in the name list
  1962. X     */
  1963. X    if (names)
  1964. X        for (i = 0;  names[i] != NULL;  i++)
  1965. X        {
  1966. X            Debug(DPARS|DEXT,
  1967. X                ("get_num, compare(%s,%s)\n", names[i], temp))
  1968. X            if (!nocase_strcmp(names[i], temp))
  1969. X            {
  1970. X                *numptr = i+low;
  1971. X                return ch;
  1972. X            }
  1973. X        }
  1974. X
  1975. X    /* no name list specified, or there is one and our string isn't
  1976. X     * in it.  either way: if it's all digits, use its magnitude.
  1977. X     * otherwise, it's an error.
  1978. X     */
  1979. X    if (all_digits)
  1980. X    {
  1981. X        *numptr = atoi(temp);
  1982. X        return ch;
  1983. X    }
  1984. X
  1985. X    return EOF;
  1986. X}
  1987. X
  1988. X
  1989. static int
  1990. set_element(bits, low, high, number)
  1991. X    bitstr_t    *bits;         /* one bit per flag, default=FALSE */
  1992. X    int        low;
  1993. X    int        high;
  1994. X    int        number;
  1995. X{
  1996. X    Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number))
  1997. X
  1998. X    if (number < low || number > high)
  1999. X        return EOF;
  2000. X
  2001. X    Debug(DPARS|DEXT, ("bit_set(%x,%d)\n",bits,(number-low)))
  2002. X    bit_set(bits, (number-low));
  2003. X    Debug(DPARS|DEXT, ("bit_set succeeded\n"))
  2004. X    return OK;
  2005. X}
  2006. END_OF_FILE
  2007. if test 11561 -ne `wc -c <'entry.c'`; then
  2008.     echo shar: \"'entry.c'\" unpacked with wrong size!
  2009. fi
  2010. # end of 'entry.c'
  2011. fi
  2012. echo shar: End of archive 2 \(of 3\).
  2013. cp /dev/null ark2isdone
  2014. MISSING=""
  2015. for I in 1 2 3 ; do
  2016.     if test ! -f ark${I}isdone ; then
  2017.     MISSING="${MISSING} ${I}"
  2018.     fi
  2019. done
  2020. if test "${MISSING}" = "" ; then
  2021.     echo You have unpacked all 3 archives.
  2022.     rm -f ark[1-9]isdone
  2023. else
  2024.     echo You still need to unpack the following archives:
  2025.     echo "        " ${MISSING}
  2026. fi
  2027. ##  End of shell archive.
  2028. exit 0
  2029.  
  2030. exit 0 # Just in case...
  2031.